package com.yituo.mjworld.core.web.core;

import com.alibaba.fastjson.JSONObject;
import com.sun.tools.internal.jxc.ap.Const;
import com.yituo.mjworld.core.annotation.*;
import com.yituo.mjworld.core.utils.Constant;
import com.yituo.mjworld.core.utils.ValidateTool;
import com.yituo.mjworld.mvc.entity.web.UserRole;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * sring切面类
 * <p>
 * 1.检测方法耗时
 * <p>
 * 2.接口权限检查
 * <p>
 * 使用@Aspect注解的类，Spring将会把它当作一个特殊的Bean（一个切面），也就是不对这个类本身进行动态代理
 *
 * @author xq
 * @date 2016-07-04
 */
@Aspect
@Component
public class MyInterceptor {
  /**
   * service层的统计耗时切面，类型必须为final String类型的,注解里要使用的变量只能是静态常量类型的
   * action层的统计耗时切面
   */
  public static final String POINT = "bean(*Action)";
  /**
   * 一秒，即1000ms
   */
  private static final long ONE_SECOND = 1000;

  private static Logger logger = Logger.getLogger(MyInterceptor.class);


  /**
   * 首字母大写
   *
   * @param name
   * @return
   */
  public static String firstCharUp(String name) {
    char[] cs = name.toCharArray();
    cs[0] -= 32;
    return String.valueOf(cs);
  }

  /**
   * 统计方法执行耗时Around环绕通知
   *
   * @param joinPoint
   * @return
   */
  @Around(POINT)
  public void timeAround(ProceedingJoinPoint joinPoint) {
    // 1.获取参数列表
    Object[] args = joinPoint.getArgs();
    MyHttpRequest request = (MyHttpRequest) args[0];
    MyHttpResponse response = (MyHttpResponse) args[1];

    // 2.获取执行的方法
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    String point = joinPoint.getTarget().getClass().getSimpleName() + "." + signature.getName();
    Method method = signature.getMethod();

    // 3.过滤测试接口
    if (!Constant.IS_DEV && method.isAnnotationPresent(Dev.class)) {
      response.setStatus(404);
      return;
    }

    try {
      // 3.验证token,以及用户访问接口的权限和以及表单
      if (validateToken(request, response, method)
          && validateAuth(request, response, method)
          && validateParam(request, response, method)
          && validateRule(request, response, method)
          && validateMethod(request, response, method)) {
        long startTime = System.currentTimeMillis();
        joinPoint.proceed(args);
        long endTime = System.currentTimeMillis();
        // 4.打印耗时的信息
        this.printExecTime(point, startTime, endTime);
      }
    } catch (Exception e) {
        e.printStackTrace();
        logger.error("统计某方法执行耗时环绕通知出错");
    } catch (Throwable e) {
      e.printStackTrace();
        logger.error("统计某方法执行耗时环绕通知出错");
    }
  }

  /**
   * 验证方法token
   * 访问的方法免token,放行.
   * 访问的方法不免token,拦截.
   *
   * @return 校验通过返回真, 校验失败返回假
   */
  private boolean validateToken(MyHttpRequest request, MyHttpResponse response, Method method) throws IOException {
    return true;
//    if (method.isAnnotationPresent(NoToken.class)) {
//      // 免token 接口,直接放行
//      return true;
//    }
//    // 非免token 接口.则token 不能为空!
//    String token = request.getHeader("token");
//    if (StringUtils.isEmpty(token)) {
//      // 非法访问,没有权限
//      response.failedFor401("当前接口需要授权!请先登录获取授权后再进行操作!");
//      return false;
//    }
//    return true;
  }

  /**
   * 验证参数.
   *
   * @return 表单验证返回真, 表单验证失败返回假
   */
  private boolean validateParam(MyHttpRequest request, MyHttpResponse response, Method method) throws IOException {
    if (method.isAnnotationPresent(Check.class)) {
      // 1.获取表单类的全路径名称
      Check check = method.getAnnotation(Check.class);
      // 2.创建对应的表单实例
      Class clazz = check.form();
      // 3.该方法配置了
      if (clazz.equals(String.class)) {
        response.failedFor301("编码错误:未指定表单!");
        return false;
      }
      BaseForm baseForm = null;
      try {
        baseForm = (BaseForm) clazz.newInstance();
      } catch (InstantiationException e) {
        e.printStackTrace();
        response.failedFor301("实例创建错误:无法为" + clazz.toString() + "创建实例!");
        return false;
      } catch (IllegalAccessException e) {
        e.printStackTrace();
        response.failedFor301("编码错误:" + clazz.toString() + "不能创建实例!");
        return false;
      }
      // 2.初始化表单
      Map<String, Object> params = request.getParameterMap();
      if (!baseForm.init(baseForm, params)) {
        response.failedFor302(baseForm.getErrorMsg());
        return false;
      }
      // 3.验证表单
      if (!baseForm.validate()) {
        response.failedFor302(baseForm.getErrorMsg());
        return false;
      }
      // 4.注入到request对象中
      request.setForm(baseForm);
      return true;
    } else {
      // 不需要参数检查,直接放行.
      return true;
    }
  }


  /**
   * 验证权限
   *
   * @return 验证通过返回真, 验证失败返回假
   */
  private boolean validateAuth(MyHttpRequest request, MyHttpResponse response, Method method) throws IOException {
    // 需要认证权限
    if (method.isAnnotationPresent(Auth.class)) {
      Auth auth = method.getAnnotation(Auth.class);
      /**
       * 超级管理员
       */
      boolean admin = auth.admin_user();

      /**
       * 维修点管理员
       */
      boolean shop_manager = auth.shop_manager();
      /**
       * 仓库管理员
       */
      boolean store_manager = auth.store_manager();
      /**
       * 省级代理
       */
      boolean pro_agent = auth.pro_agent();
      /**
       * 市级代理
       */
      boolean city_agent = auth.city_agent();
      /**
       * 县级代理
       */
      boolean area_agent = auth.area_agent();
      /**
       * 代理助手
       */
      boolean angent_helper = auth.angent_helper();
      /**
       * 供货商
       */
      boolean supplier = auth.supplier();
      /**
       * 全部用户可访问
       */
      boolean all = auth.all();
      /**
       * 可访问的用户列表
       */
      int[] arr = new int[]{
          admin ? UserRole.SUPER_ADMIN : -1,
          shop_manager ? UserRole.SHOP_MAN : -1,
          store_manager ? UserRole.STORE_MANAGER : -1,
          pro_agent ? UserRole.PRO_AGENT : -1,
          city_agent ? UserRole.CITY_AGENT : -1,
          area_agent ? UserRole.AREA_AGENT : -1,
          angent_helper ? UserRole.NORMAL_ADMIN : -1,
          supplier ? UserRole.SUPPLIER : -1,
      };
      if (all) {
        // 全部用户可访问,直接放行
        return true;
      }
      for (int i = 0; i < arr.length; i++) {
        if (request.getCurrentUser().getRule() == arr[i]) {
          return true;
        }
      }
      response.failedFor401("您当前的权限不能访问该接口!");
      return false;
    } else {
      // 该方法没有Auth拦截,不校验,放行
      return true;
    }
  }

  /**
   * 对含有@Rule 注解的方法进行校验
   * 访问的方法免token,放行.
   * 访问的方法不免token,拦截.
   *
   * @return 校验通过返回真, 校验失败返回假
   */
  private boolean validateRule(MyHttpRequest request, MyHttpResponse response, Method method) throws IOException {
    if (method.isAnnotationPresent(Rule.class)) {
      Rule rule = method.getAnnotation(Rule.class);

      String name_zn = rule.name_zn();
      // 欲检查的字段值
      String name = rule.name();
      if (StringUtils.isEmpty(name)) {
        response.failedFor301("配置错误,检查的内容不能为空!");
        return false;
      }
      // 默认值
      String defaultValue = rule.defaultValue();
      // 其他校验项
      String sMin = rule.min();
      String sMax = rule.max();
      int length = rule.length();
      boolean required = rule.required();
      boolean mobile = rule.mobile();
      int min = 0;
      int max = 0;
      Class type = rule.type();
      try {
        if (!"null".equals(sMin)) {
          min = Integer.parseInt(sMin);
        }
        if (!"null".equals(sMax)) {
          max = Integer.parseInt(sMax);
        }
      } catch (Exception e) {
        // 开发者的配置出错.
        e.printStackTrace();
        response.failedFor301("配置错误,最大值最小值不能为非字符串!");
        return false;
      }
      // 设置默认值
      String fieldValue = request.getParameter(name);
      if (!StringUtils.isEmpty(defaultValue)) {
        fieldValue = defaultValue;
      }
      // 必填项校验
      if (required && ValidateTool.isEmpty(fieldValue)) {
        response.failedFor302(name_zn + "为必填项!");
        return false;
      }
      // 4.长度校验
      if (length >= 0 && ValidateTool.length(fieldValue, length)) {
        response.failedFor302(name_zn + "长度不符合");
        return false;
      }
      // 5.校验是否最小值
      if (fieldValue != null && !"null".equals(sMin) && ValidateTool.min(fieldValue, min)) {
        response.failedFor302(name_zn + "小于最小值!");
        return false;
      }
      // 6.校验最大值
      if (fieldValue != null && !"null".equals(sMax) && ValidateTool.max(fieldValue, max)) {
        response.failedFor302(name_zn + "大于最大值!");
        return false;
      }
      // 7.校验手机号码
      if (mobile && !ValidateTool.Mobile(fieldValue)) {
        response.failedFor302(name_zn + "不是手机号码!");
        return false;
      }
    }
    return true;
  }

  /**
   * 验证方法是否可访问
   * 访问的方法上被标注@ban注解的响应404
   *
   * @return 校验通过返回真, 校验失败返回假
   */
  private boolean validateMethod(MyHttpRequest request, MyHttpResponse response, Method method) throws IOException {
    if (method.isAnnotationPresent(Ban.class)) {
      response.failedFor404("接口已经关闭!");
      return false;
    }
    return true;
  }

  /**
   * 打印方法执行耗时的信息，如果超过了一定的时间，才打印
   *
   * @param methodName
   * @param startTime
   * @param endTime
   */
  private void printExecTime(String methodName, long startTime, long endTime) {
    long diffTime = endTime - startTime;
    if (diffTime > ONE_SECOND) {
      logger.warn(methodName + " 接口执行耗时:" + diffTime + " ms");
    } else {
      logger.info(methodName + " 接口执行耗时:" + diffTime + " ms");
    }
  }
}